#include "Wt.h"

/* Initialize the object ID map */

void WtInitPtrRegistry(WtContext *w)
{
  Tcl_InitObjHashTable(&w->objRegistry.idMap);
}

void WtDeletePtrRegistry(WtContext *w)
{
  Tcl_DeleteHashTable(&w->objRegistry.idMap);
}

/* Get an object by ID */

Tcl_Obj *WtGetPtrById(Tcl_Obj *id, Tcl_ObjType *type)
{
  Tcl_Obj *obj;
  WtContext *w = WtGetContext();
  Tcl_HashEntry *hashEntry;

  WtApLog(APLOG_MARK, APLOG_NOERRNO|APLOG_DEBUG, w,
    "Wtcl: WtGetPtrById: id = 0x%x", id);

  hashEntry = Tcl_FindHashEntry(&w->objRegistry.idMap, (char *)id);

  if (hashEntry) {
    obj = (Tcl_Obj *)(Tcl_GetHashValue(hashEntry));

    WtApLog(APLOG_MARK, APLOG_NOERRNO|APLOG_DEBUG, w,
      "Wtcl: WtGetPtrById found obj: obj = 0x%x", obj);

    if (obj && (!type || obj->typePtr == type)) {
      return obj;
    }
  }

  return NULL;
}

/* Get the object that is being referenced */

Tcl_Obj *WtGetPtr(Tcl_Obj *obj, Tcl_ObjType *type)
{
  if (type && obj->typePtr == type) {
    return obj;
  }

  return WtGetPtrById(obj, type);
}

/* Set the id of an object */

void WtSetPtrId(Tcl_Obj *obj, const char *str, int len)
{
  WtPtrState *ptrState;
  Tcl_HashEntry *hashEntry;
  int created;
  Tcl_Obj *old;
  WtContext *w = WtGetContext();

  /* Free the old ID */

  WtDeletePtrId(obj);

  /* Invalidate the Tcl string */

  Tcl_InvalidateStringRep(obj);

  /* Set the new ID */

  if (str) {
    if (len == -1) {
      len = strlen(str);
    }
    ptrState = obj->internalRep.twoPtrValue.ptr2;

    if (!ptrState) {
      ptrState = (WtPtrState *)ckalloc(sizeof(WtPtrState));
      obj->internalRep.twoPtrValue.ptr2 = ptrState;
    }

    ptrState->id = Tcl_NewStringObj(str, len);
    Tcl_IncrRefCount(ptrState->id);

    /* Store the ID in the map */

    hashEntry = Tcl_CreateHashEntry(&w->objRegistry.idMap,
      (char *)ptrState->id, &created);

    if (hashEntry) {

      /* Steal the ID if it already exists */

      if (!created) {
        old = (Tcl_Obj *)(Tcl_GetHashValue(hashEntry));
        if (old && old != obj) {
          WtDeletePtrId(old);
        }
      }

      Tcl_SetHashValue(hashEntry, obj);
    }
  }
}

void WtSetPtrIdObj(Tcl_Obj *obj, Tcl_Obj *id)
{
  char *bytes;
  int len;

  bytes = Tcl_GetStringFromObj(id, &len);
  WtSetPtrId(obj, bytes, len);
}

/* Unset the id */

void WtDeletePtrId(Tcl_Obj *obj)
{
  WtPtrState *ptrState;
  WtContext *w;
  Tcl_HashEntry *hashEntry;

  /* Free the old ID */

  ptrState = obj->internalRep.twoPtrValue.ptr2;
  if (ptrState) {
    if (ptrState->id) {
      w = WtGetContext();
      hashEntry = Tcl_FindHashEntry(&w->objRegistry.idMap, (char *)ptrState->id);
      if (hashEntry) {
        Tcl_DeleteHashEntry(hashEntry);
      }
      Tcl_DecrRefCount(ptrState->id);
      ptrState->id = NULL;
    }
  }
}

/* Delete an object from the registry */

void WtDeletePtr(Tcl_Obj *obj)
{
  WtPtrState *ptrState;

  WtDeletePtrId(obj);

  ptrState = obj->internalRep.twoPtrValue.ptr2;

  if (ptrState) {
    ckfree(ptrState);
    obj->internalRep.twoPtrValue.ptr2 = NULL;
  }
}

/* Update the string representation of a pointer object */

void WtUpdatePtrString(Tcl_Obj *obj)
{
  WtContext *w = WtGetContext();
  char buf[1024];
  int n;
  char *bytes;
  WtPtrState *ptrState;

  WtApLog(APLOG_MARK, APLOG_NOERRNO|APLOG_DEBUG, w,
    "Wtcl: WtUpdatePtrString: obj = 0x%x", obj);

  ptrState = obj->internalRep.twoPtrValue.ptr2;

  if (!ptrState || !ptrState->id) {

    /* Format an ID based on the pointer address */

    WtApLog(APLOG_MARK, APLOG_NOERRNO|APLOG_DEBUG, w,
      "Wtcl: Formatting ID based on pointer address.");

    n = ap_snprintf(buf, sizeof(buf), "%s-0x%x", obj->typePtr->name, obj);
    if (!(n >= 0 && n < sizeof(buf))) {
      n = ap_snprintf(buf, sizeof(buf), "0x%x", obj);
    }

    WtApLog(APLOG_MARK, APLOG_NOERRNO|APLOG_DEBUG, w,
      "Wtcl: n = %d", n);

    WtSetPtrId(obj, buf, n);

    obj->bytes = Tcl_Alloc(n + 1);
    memcpy(obj->bytes, buf, n + 1);
    obj->length = n;

  } else {

    /* Create string from the stored ID */

    WtApLog(APLOG_MARK, APLOG_NOERRNO|APLOG_DEBUG, w,
      "Wtcl: Object has no ID.");

    bytes = Tcl_GetStringFromObj(ptrState->id, &n);

    obj->bytes = Tcl_Alloc(n + 1);
    memcpy(obj->bytes, bytes, n + 1);
    obj->length = n;

  }
}

/* Object conversion */

int WtSetPtrFromAny(Tcl_Interp *interp, Tcl_Obj *obj, Tcl_ObjType *type)
{
  Tcl_Obj *ref;

  if (obj->typePtr == type) {
    return TCL_OK;
  }

  if (ref = WtGetPtr(obj, type)) {
    if (ref->typePtr && ref->typePtr->dupIntRepProc) {
      if (obj->typePtr && obj->typePtr->freeIntRepProc) {
        obj->typePtr->freeIntRepProc(obj);
      }
      ref->typePtr->dupIntRepProc(ref, obj);
      return TCL_OK;
    }
  }

  return TCL_ERROR;
}
